/*******************************************************************************
 * This file is part of blueprint4j www.blueprint4j.org.
 * 
 *     blueprint4j is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     blueprint4j is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with blueprint4j. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package blueprint4j.utils;

import java.awt.BorderLayout;
import java.awt.Font;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;

import blueprint4j.comm.io.LineReader;
import blueprint4j.comm.io.LineReaderBase64;
import blueprint4j.comm.io.LineReaderDelimeter;
import blueprint4j.comm.io.LineWriter;
import blueprint4j.comm.io.LineWriterBase64;
import blueprint4j.comm.io.LineWriterDelimiter;

public class Utils {
    
    private static final String MODE_NORMAL = "normal",MODE_DEBUG="debug",MODE_STRESSTEST = "stresstest";
    private static final String RUN_MODE = Settings.getString("run.mode",MODE_NORMAL);
    public static final boolean RUN_MODE_NORMAL = RUN_MODE.equals(MODE_NORMAL);
    public static final boolean RUN_MODE_DEBUG = RUN_MODE.equals(MODE_DEBUG);
    public static final boolean RUN_MODE_STRESSTEST = RUN_MODE.equals(MODE_STRESSTEST);
    
    public static final boolean OS_WINDOWS = (System.getProperty("os.name").indexOf("Windows")!=-1);
    public static final boolean OS_LINUX = (System.getProperty("os.name").indexOf("Linux")!=-1);
    
    public static final long MINUTE = 1000*60,HOUR = MINUTE*60,DAY=HOUR*24,WEEK=DAY*7,MONTH=WEEK*4,YEAR=MONTH*12;
    
    public static final Date TIME_HOUR[]=new Date[12];
    
    static {
        try {
            for (int i =0;i <12;i++) {
                TIME_HOUR[i]=new SimpleDateFormat("HH").parse(""+i);
            }
        } catch (Throwable th) {
            Log.critical.out(th);
        }
    }
    
    public Utils() {}
    
    private static Calendar calendar = Calendar.getInstance();
    
    public static boolean SERVER_INSTANCE = Settings.getBoolean("SERVER.INSTANCE",false);
    public static final String SERVER_HOST = Settings.getString("server.host.ip","localhost");
    public static final int SERVER_RMI_PORT = 1099;
    
    public static StackTraceElement[] getStackTrace() {
        return new Exception("Read Stacktrace").getStackTrace();
    }
    
    public static String getStackTraceStr() {
        StackTraceElement[] element = getStackTrace();
        String trace = "";
        for (int i = 0;i < element.length;i++) {
            trace += element[i].toString()+"\n";
        }
        return trace;
    }
    
    public static boolean isServerRmiUp() {
        try {
            java.net.Socket s = new java.net.Socket(SERVER_HOST,SERVER_RMI_PORT);
            s.close();
            return true;
        } catch (Exception exception) {
            return false;
        }
    }
    
    static final String[] HEX_PARTS=new String[]{"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"};
    static final String HEX_STRING="0123456789ABCDEF";
    
    public static byte[] fromHEX(String hex){
    	if ((hex.length()&1)!=0){
    		hex="0"+hex;
    	}
    	byte[] buf=new byte[hex.length()/2];
    	int pos=0;
    	for (char c:hex.toUpperCase().toCharArray()){
    		int value=HEX_STRING.indexOf(c);
    		if (value==-1){
    			return null;
    		}else{
    			if ((pos&1)==0){
    				buf[pos/2]=(byte)(value&15);
    			}else{
    				buf[pos/2]|=(byte)((value&15)<<4);
    			}
    		}
    		pos++;
    	}
    	return buf;
    }

    public static String toHEX(byte[] bytes){
    	StringBuffer sb=new StringBuffer();
    	for (byte b:bytes){
    		sb.append(HEX_PARTS[(b&0xf0)>>4]);
    		sb.append(HEX_PARTS[b&0xf]);
    	}
    	return sb.toString();
    }
    
    public static long getClassModifiedTime(Class c)
    throws IOException {
    	long configDateTime=Settings.getLong("class.forced_age", -1);
    	
    	if (configDateTime!=-1){
    		return configDateTime;
    	}
    	
        ClassLoader cl=c.getClassLoader();
        String class_file_name=c.getName().replaceAll("[.]","/");
        class_file_name = class_file_name+".class";
        URL u=cl.getResource(class_file_name);
        if (u.toString().startsWith("file:/")){
        	try {
        		File file=new File(u.toURI());
        		return file.lastModified();
        	} catch (URISyntaxException ue){
        		throw new IOException(ue.getMessage());
        	}
        }else{
	        String jar_file_name=u.toString().substring(9,u.toString().indexOf("!")).replaceAll("%20"," ");
	        JarFile jf=new JarFile(jar_file_name);
	        JarEntry je=jf.getJarEntry(class_file_name);
	        return je.getTime();
        }
    }
    
    public static String replaceAll(String source, String find_str, String replace_str) {
        String buffer = "";
        while (source.indexOf(find_str) != -1) {
            buffer = buffer + source.substring(0, source.indexOf(find_str)) + replace_str;
            source = source.substring(source.indexOf(find_str) + find_str.length());
        }
        return buffer + source;
    }
    
    public static boolean isValidMsisdn(String msisdn) {
        if (msisdn !=null && msisdn.startsWith("+")){
            return isANumber(msisdn.substring(1)) && msisdn.length() > 3;
        }
        return isANumber(msisdn) && msisdn.length() > 2;
    }
    
    public static boolean isANumber(String num) {
        try {
            Long.parseLong(num);
            return true;
        } catch (Exception ex) {
            return false;
        }
    }
    
    public static String readFromStream(InputStream stream)
    throws IOException {
        String str = "";
        byte buffer[] = new byte[1024];
        while (stream.available() > 0) {
            int r = stream.read(buffer);
            str += new String(buffer,0,r);
        }
        stream.close();
        return str;
    }
    
    public static byte[] readByteFromStream(InputStream instream)
    throws IOException {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        byte buffer[] = new byte[1024];
        while (instream.available() > 0) {
            int r = instream.read(buffer);
            stream.write(buffer,0,r);
        }
        return stream.toByteArray();
    }
    
    /**
     * Reads from the stream untill the stop data is found or read amount if read
     * timeout times out in seconds if no data has been found
     */
    public static byte[] readFromStream(InputStream stream,byte stop_data[],int read_amount,int timeout)
    throws IOException {
        byte buffer[] = new byte[]{};
        for (int i =0;i < timeout;i++) {
            if (stream.available() == 0) {
                try {Thread.sleep(1000);} catch (InterruptedException ie) {}
            } else {
                buffer = ByteM.concat(buffer,new byte[]{(byte)stream.read()});
                i = 0;
                if (ByteM.indexOf(buffer,stop_data)!=-1) {
                    return ByteM.subbyte(buffer,buffer.length-stop_data.length);
                }
                if (read_amount > 0 && buffer.length > read_amount) {
                    return null;
                }
            }
        }
        return null;
    }
    
    /**
     * Reads from the stream untill the stop data is found
     * timeout times out in seconds if no data has been found
     */
    public static byte[] readFromStream(InputStream stream,byte stop_data[],int timeout)
    throws IOException {
        return readFromStream(stream,stop_data,-1,timeout);
    }
    
    
    /**
     * This wil first clear the input stream, then write the command, then read response
     */
    public static byte [] writeAndReadCommand(InputStream instream,OutputStream outstream,byte command[],int sleep,int wait_time)
    throws IOException {
        readFromStream(instream,1024,sleep,wait_time);
        outstream.write(command);
        return readFromStream(instream,1024,sleep,wait_time);
    }
    
    public static byte[] readFromStream(InputStream stream,int buffer_size,int sleep,int wait_time)
    throws IOException {
        byte buffer[] = new byte[buffer_size];
        byte str_buffer[] = new byte[]{};
        long start_time = System.currentTimeMillis();
        while (stream.available() > 0 || System.currentTimeMillis() < start_time + wait_time) {
            if (stream.available() > 0) {
                start_time = System.currentTimeMillis();
                int r = stream.read(buffer);
                if (r == -1) {
                    throw new IOException("Read on stream returned -1 indicating that the stream has been closed");
                }
                str_buffer = ByteM.concat(str_buffer,ByteM.subbyte(buffer,0,r));
                //str_buffer += new  String(buffer,0,r);
            }
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException ie) {}
        }
        return str_buffer;
    }
    
    public static File writeStreamToFile(InputStream instream)
    throws IOException {
        File file = File.createTempFile("blueprint4j","tool");
        readWrite(instream,new FileOutputStream(file));
        return file;
    }
    
    public static void readWrite(InputStream in,OutputStream out,boolean close_out)
    throws IOException {
        byte buffer[] = new byte[1024];
        for (;in.available() > 0;) {
            int r = in.read(buffer);
            if (r > 0) {
                out.write(buffer,0,r);
            }
        }
        in.close();
        if (close_out) {
            out.close();
        }
    }
    
    public static void readWrite(InputStream in,OutputStream out)
    throws IOException {
        readWrite(in,out,true);
    }
    
    private static String concatLines(String lines)
    throws IOException  {
        String result = "";
        LineNumberReader reader = new LineNumberReader(new InputStreamReader(new ByteArrayInputStream(lines.getBytes())));
        for (String line = reader.readLine();line != null;line = reader.readLine())
            result += line;
        return result;
    }
    
    public static int[] convertDecimalToHourMinSec(double d) {
        double d_floor = Math.abs((d < 0? Math.ceil(d) : Math.floor(d)));
        double d_rest = Math.abs(d) - d_floor;
        return new int[]{
            (int)Math.round(d_floor*(d < 0?-1:1)),
            (int)Math.round(Math.floor(d_rest*60)),
            (int)Math.round(Math.floor(d_rest*60*60) - Math.round(Math.floor(d_rest*60))*60)
        };
    }
    
    public static Date convertGMT_TO_LOCAL(Date date) {
        return new Date(date.getTime() + TimeZone.getDefault().getRawOffset());
    }
    
    public static Date convertLOCAL_TO_GMT(Date date) {
        return new Date(date.getTime() - TimeZone.getDefault().getRawOffset());
    }
    
    public static byte calculateCheckSum(byte bytes[]) {
        byte chk= 0;
        for (int i = 0;i < bytes.length;i++) {chk = (byte)(chk ^ bytes[i]);}
        return chk;
    }
    
    public static boolean isInteger(String number) {
        try {
            Integer.parseInt(number);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }
    
    public static JDialog createDialog(JFrame owner,String title,String message,boolean modal) {
        JDialog dialog = new JDialog(owner,title,modal);
        JLabel label = new JLabel(message);
        label.setFont(new Font("Arial", Font.PLAIN, 12));
        dialog.getContentPane().setLayout(new BorderLayout());
        dialog.getContentPane().add(label,BorderLayout.CENTER);
        dialog.setLocationRelativeTo(owner);
        dialog.pack();
        return dialog;
    }
    
    public static double calculateAngle(double x1,double y1,double x2,double y2) {
        double theta = 0;
        x2-=x1;
        y2-=y1;
        if (x2 != 0) {
            theta = Math.atan(y2/x2);
            if (x2<0){
                theta+=Math.PI;
            }
        } else {
            if (y2<0){
                theta = Math.PI/2;
            }else{
                theta = -Math.PI/2;
            }
        }
        return theta;
    }
    
    public static double convertRadToDeg(double rad) {
        return Math.toDegrees(rad);
    }
    
    public static String removeDataInside(String data,String del) {
        if (data.indexOf(del)!=-1 && data.indexOf(del) != data.lastIndexOf(del)) {
            String tmp = data.substring(0,data.indexOf(del));
            data = data.substring(data.indexOf(del)+del.length());
            data = data.substring(data.indexOf(del)+del.length());
            return removeDataInside(tmp + data,del);
        } else {
            if (data.indexOf(del) != -1) {
                return data.substring(0,data.indexOf(del));
            } else {
                return data;
            }
        }
    }
    
    public static int calculateBase64Width(int plain) {
        plain = plain + (plain /3);
        plain = plain % 4 > 0?((plain / 4)+1)*4:((plain / 4))*4;
        return plain;
    }
    
    /**
     * This method will replace a leading zero with the correct
     * country code
     */
    public static String correctMsisdn(String msisdn) {
        if (msisdn != null) {
            if (msisdn.startsWith("0")) {
                msisdn = Settings.getString("local.country_code","+27")+msisdn.substring(1);
            }
            if (!msisdn.startsWith("+")) {
                msisdn = "+"+msisdn;
            }
        }
        return msisdn;
    }
    
    public byte[] writeObject(Object serial)
    throws IOException {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(stream);
        oo.writeObject(serial);
        return stream.toByteArray();
    }
    
    public Object readObject(InputStream stream)
    throws IOException,ClassNotFoundException {
        ObjectInputStream oo = new ObjectInputStream(stream);
        return oo.readObject();
    }
    
    /**
     * Packs the original (orig) string to length of length by adding
     * ch to the front
     */
    public static String pack(String orig,String ch,int length) {
        String str = orig;
        for (;str.length() < length;str = ch+str);
        return str;
    }
    
    /**
     * Packs the original (orig) string to length of length by adding
     * ch to the end
     */
    public static String packAfter(String orig,String ch,int length) {
        String str = orig;
        for (;str.length() < length;str = str+ch);
        return str;
    }
    
    /**
     * Converts a lexigraphical number into a normal one
     */
    public static boolean isLexGreaterEqualsThan(String lex1,String lex2) {
        VectorString vs1 = new VectorString(lex1,".");
        VectorString vs2 = new VectorString(lex2,".");
        for (int i = 0;i < (vs1.size() < vs2.size()?vs1.size():vs2.size());i++) {
            if (Integer.parseInt(vs1.get(i)) < Integer.parseInt(vs2.get(i))) {
                return false;
            }
            if (Integer.parseInt(vs1.get(i)) > Integer.parseInt(vs2.get(i))) {
                return true;
            }
        }
        if (vs1.size() < vs2.size()) {
            return false;
        }
        return true;
    }
    
    public static boolean isLexGreaterThan(String lex1,String lex2) {
        VectorString vs1 = new VectorString(lex1,".");
        VectorString vs2 = new VectorString(lex2,".");
        for (int i = 0;i < (vs1.size() < vs2.size()?vs1.size():vs2.size());i++) {
            if (Integer.parseInt(vs1.get(i)) < Integer.parseInt(vs2.get(i))) {
                return false;
            }
            if (Integer.parseInt(vs1.get(i)) > Integer.parseInt(vs2.get(i))) {
                return true;
            }
        }
        return false;
    }
    
    private static int findGCD(int sum) {
        int i = 1;
        for (;i <= sum;i = i << 1);
        return (i>>1);
    }
    
    /**
     * converts a number to its bits
     * 123 = 1101111
     */
    public static short[] toBitArray(int num) {
        short [] arr = new short[255];
        int cnt = 0;
        for (int i = findGCD(num);i > 0;i = i >> 1) {
            if (i <= num) {
                num-= i;
                arr[cnt++]=1;
            } else {
                arr[cnt++]=0;
            }
        }
        short ret[] = new short[cnt];
        for (int i =0;i < cnt;i++) {
            ret[i] = arr[cnt-1-i];
        }
        return ret;
    }
    
    public static String convertToNumberOfLength(long number,int length) {
        String num = ""+number;
        for (int i =0;i < length-num.length();i++) {
            num = "0"+num;
        }
        return num;
    }
    
    private static String getUniqueFileName(int cnt,String prefix,String postfix,File dir[]) {
        if (cnt++ > 10000) {
            Log.critical.out("Stuck in a loop trying to find a unique filename");
            System.exit(-1);
        }
        boolean failed = false;
        String filename = prefix+(Math.round(Math.random()*100000))+postfix;
        for (int i = 0;i < dir.length && !failed;i++) {
            System.out.println("Get Unique filename = " + dir[i].getAbsolutePath());
            if (new File(dir[i].getAbsolutePath()+"/"+filename).exists()) {
                failed = true;
            }
        }
        if (!failed) {
            return filename;
        }
        return getUniqueFileName(cnt,prefix,postfix,dir);
    }
    
    /**
     * This will create a unique filename file for all specified directories
     * This will guarantee uniqueness for a 100 000 files
     */
    public static String getUniqueFileName(String prefix,String postfix,File dir[]) {
        return getUniqueFileName(0,prefix,postfix,dir);
    }
    
    public static void deleteDirectory(File directory)
    throws IOException {
        if (!directory.isDirectory()) {
            throw new IOException(directory.getAbsolutePath() + " is not a directory");
        }
        File list[] = directory.listFiles();
        for (int i = 0;i <list.length;i++) {
            if (list[i].isDirectory()) {
                deleteDirectory(list[i]);
            }
            list[i].delete();
        }
    }
    
    public static boolean isWindows() {
        return System.getProperty("os.name").indexOf("Windows")!=-1;
    }
    
    public static Clip getAudioClip(String resource_name)
    throws Exception {
        AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(ClassLoader.getSystemResourceAsStream(resource_name));
        AudioFormat	format = audioInputStream.getFormat();
        DataLine.Info	info = new DataLine.Info(Clip.class, format);
        Clip m_clip = (Clip) AudioSystem.getLine(info);
        m_clip.open(audioInputStream);
        audioInputStream.close();
        return m_clip;
    }
    
    public static void playSound(String resource_name,int times)
    throws Exception {
        getAudioClip(resource_name).loop(times);
    }
    
    /**
     * Convert an exception to a string
     */
    public static String convert(Throwable exception) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        exception.printStackTrace(new PrintStream(stream));
        return new String(stream.toByteArray());
    }
    
    /**
     * Copy a system resource directory to a destination directory
     */
    public static void copySystemResource(String resource_name,File destination) 
    throws java.net.URISyntaxException,java.io.FileNotFoundException,java.io.IOException {
        Log.debug.out("Load resource" + resource_name + " "+ ClassLoader.getSystemResourceAsStream(resource_name));
        File file = new File(ClassLoader.getSystemResource(resource_name).toURI());
        if (file.isDirectory()) {
            destination.mkdirs();
            File files[] = file.listFiles();
            for (int i =0;i <files.length;i++) {
                copySystemResource(resource_name+"/"+files[i].getName(),new File(destination.getAbsolutePath() + "/" + files[i].getName()));
            }
        } else {
            Utils.readWrite(ClassLoader.getSystemResourceAsStream(resource_name),new FileOutputStream(destination));
        }
    }
    
    public static void main(String args[]) throws Exception {
        System.out.println(new String(Base64.encode("".getBytes()))+":");
        System.exit(-1);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        LineWriter writer = new LineWriterDelimiter(
                new LineWriterDelimiter(
                new LineWriterBase64(stream),"<T>".getBytes()
                ),"<R>".getBytes());
        writer.writeLineStr("Hello World");
        ByteArrayInputStream instream = new ByteArrayInputStream(stream.toByteArray());
        LineReader reader = new LineReaderBase64(
                new LineReaderDelimeter(
                new LineReaderDelimeter(instream,"<R>".getBytes()),"<T>".getBytes())
                );
        System.out.println(new String(stream.toByteArray()) + " = " + reader.readLineStr());
        
        int lastlen = 0;
        System.out.println(new String(Base64.decode("MjAw".toCharArray())));
        for (int i =0;i < 1;i++) {
            String enc = new String(Base64.encode(("\n").getBytes()));
            if (enc.length() != lastlen) {
                lastlen = enc.length();
                System.out.println(i+ " = " + enc + " " + enc.length());
            }
        }
    }
    
}

